/**
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_ECMA_MACRO_ACCESSORS_H
#define ECMASCRIPT_ECMA_MACRO_ACCESSORS_H

#include "runtime/include/hclass.h"
#include "plugins/ecmascript/runtime/common.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/mem/barriers-inl.h"
#include "utils/logger.h"

namespace ark::ecmascript {

using JSAccessorsMaskType = uint32_t;

template <size_t OFFS>
struct JSAccessorsOffs {
    static constexpr size_t END = OFFS;
};

template <template <int, typename X = void> typename A, int IDX>
struct JSAccessorsObjectField {
    static constexpr size_t OFFS = A<IDX - 1>::END;
    static constexpr size_t END = OFFS + sizeof(::ark::ecmascript::JSTaggedType);

    static constexpr JSAccessorsMaskType NATIVE_MASK = A<IDX - 1>::NATIVE_MASK;

    static_assert(OFFS % alignof(JSTaggedType) == 0);  // if not then try to shuffle fields
};

template <template <int, typename X = void> typename A, int IDX, typename T>
struct JSAccessorsFixedSizeField {
    static constexpr size_t OFFS = A<IDX - 1>::END;
    static constexpr size_t END = OFFS + sizeof(T);

    static constexpr JSAccessorsMaskType NATIVE_MASK =
        A<IDX - 1>::NATIVE_MASK | HClass::FieldOffsetToMask(A<IDX>::OFFS);

    static_assert(OFFS % alignof(T) == 0);
};

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_START(offs)                                 \
    template <int I, typename _X = void>                      \
    struct JSAccessorsField {                                 \
    };                                                        \
    template <typename _X>                                    \
    struct JSAccessorsField<-1, _X> : JSAccessorsOffs<offs> { \
        static constexpr JSAccessorsMaskType NATIVE_MASK = 0; \
    };

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_BASE(base)                                                         \
    template <int I, typename _X = void>                                             \
    struct JSAccessorsField {                                                        \
    };                                                                               \
    template <typename _X>                                                           \
    struct JSAccessorsField<-1, _X> : JSAccessorsOffs<base::SIZE> {                  \
        static constexpr JSAccessorsMaskType NATIVE_MASK = base::NATIVE_FIELDS_MASK; \
    };

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_FINISH(idx)                                                                \
    template <typename _X>                                                                   \
    struct JSAccessorsField<idx, _X> : JSAccessorsOffs<JSAccessorsField<idx - 1>::END> {     \
    };                                                                                       \
    static constexpr size_t SIZE = AlignUp(JSAccessorsField<idx>::END, alignof(TaggedType)); \
    static constexpr JSAccessorsMaskType NATIVE_FIELDS_MASK = JSAccessorsField<idx - 1>::NATIVE_MASK;

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_EMPTY(offs) ACCESSORS_START(offs) ACCESSORS_FINISH(0)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_BASE_EMPTY(base) ACCESSORS_BASE(base) ACCESSORS_FINISH(0)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS(idx, name)                                                                                           \
    template <typename _X>                                                                                             \
    struct JSAccessorsField<idx, _X> : JSAccessorsObjectField<JSAccessorsField, idx> {                                 \
    };                                                                                                                 \
    static constexpr size_t Get##name##Offset()                                                                        \
    {                                                                                                                  \
        return JSAccessorsField<idx>::OFFS;                                                                            \
    }                                                                                                                  \
    JSTaggedValue Get##name() const                                                                                    \
    {                                                                                                                  \
        /* Note: We can't statically decide the element type is a primitive or heap object, especially for */          \
        /*       dynamically-typed languages like JavaScript. So we simply skip the read-barrier.          */          \
        return JSTaggedValue(Barriers::GetDynValue<JSTaggedType>(this, Get##name##Offset()));                          \
    }                                                                                                                  \
    template <typename T>                                                                                              \
    void Set##name(const JSThread *thread, JSHandle<T> value, BarrierMode mode = WRITE_BARRIER)                        \
    {                                                                                                                  \
        if (mode == WRITE_BARRIER) {                                                                                   \
            ObjectAccessor::SetDynValue(thread, this, Get##name##Offset(), value.GetTaggedValue().GetRawData());       \
        } else {                                                                                                       \
            ObjectAccessor::SetDynValueWithoutBarrier(this, Get##name##Offset(), value.GetTaggedValue().GetRawData()); \
        }                                                                                                              \
    }                                                                                                                  \
    void Set##name(const JSThread *thread, JSTaggedValue value, BarrierMode mode = WRITE_BARRIER)                      \
    {                                                                                                                  \
        if (mode == WRITE_BARRIER) {                                                                                   \
            ObjectAccessor::SetDynValue(thread, this, Get##name##Offset(), value.GetRawData());                        \
        } else {                                                                                                       \
            ObjectAccessor::SetDynValueWithoutBarrier(this, Get##name##Offset(), value.GetRawData());                  \
        }                                                                                                              \
    }                                                                                                                  \
    void Set##name(JSTaggedValue value)                                                                                \
    {                                                                                                                  \
        ObjectAccessor::SetPrimitive<JSTaggedType>(this, Get##name##Offset(), value.GetRawData());                     \
    }

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_FIXED_SIZE_RAW_FIELD(name, type, sizeType, offset)          \
    static_assert(sizeof(type) <= sizeof(sizeType));                          \
    static constexpr size_t Get##name##Offset()                               \
    {                                                                         \
        return offset;                                                        \
    }                                                                         \
    inline void Set##name(type value)                                         \
    {                                                                         \
        ObjectAccessor::SetPrimitive<type>(this, Get##name##Offset(), value); \
    }                                                                         \
    inline type Get##name() const                                             \
    {                                                                         \
        return ObjectAccessor::GetPrimitive<type>(this, Get##name##Offset()); \
    }

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_FIXED_SIZE_FIELD(idx, name, type, sizeType)                                       \
    template <typename _X>                                                                          \
    struct JSAccessorsField<idx, _X> : JSAccessorsFixedSizeField<JSAccessorsField, idx, sizeType> { \
    };                                                                                              \
    ACCESSORS_FIXED_SIZE_RAW_FIELD(name, type, sizeType, JSAccessorsField<idx>::OFFS)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_NATIVE_FIELD(idx, name, type) ACCESSORS_FIXED_SIZE_FIELD(idx, name, type *, type *)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_VOID_FIELD(idx, name) ACCESSORS_FIXED_SIZE_FIELD(idx, name, void *, void *)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_PRIMITIVE_FIELD(idx, name, type) ACCESSORS_FIXED_SIZE_FIELD(idx, name, type, type)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ACCESSORS_BIT_FIELD(idx, name)                        \
    ACCESSORS_FIXED_SIZE_FIELD(idx, name, uint32_t, uint32_t) \
    inline void Clear##name()                                 \
    {                                                         \
        Set##name(0UL);                                       \
    }

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define SET_GET_BIT_FIELD(bitFieldName, name, type)                    \
    inline type Get##name() const                                      \
    {                                                                  \
        return name##Bits::Decode(Get##bitFieldName());                \
    }                                                                  \
    inline void Set##name(type t)                                      \
    {                                                                  \
        Set##bitFieldName(name##Bits::Update(Get##bitFieldName(), t)); \
    }

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FIRST_BIT_FIELD(bitFieldName, name, type, bits) \
    using name##Bits = BitField<type, 0, bits>;         \
    SET_GET_BIT_FIELD(bitFieldName, name, type)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define NEXT_BIT_FIELD(bitFieldName, name, type, bits, lastName) \
    using name##Bits = lastName##Bits::NextField<type, bits>;    \
    SET_GET_BIT_FIELD(bitFieldName, name, type)

}  // namespace ark::ecmascript

#endif  // ECMASCRIPT_ECMA_MACRO_ACCESSORS_H
